package jmine.tec.test.utils.spring;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;

import junit.framework.TestCase;

import org.hibernate.Interceptor;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.access.SingletonBeanFactoryLocator;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * @author julien
 */
public abstract class SpringTestCase extends TestCase {

    private static final String DEFAULT_TEST_FACTORY_KEY = "testFactory";

    private static final String SPRING_BEANS_XML = "jmine-tec-integration-test.xml";

    private BeanFactory ctx;

    private Session session;

    private SessionFactory sf;

    /**
     * C'tor
     */
    public SpringTestCase() {
        try {
            this.ctx = SingletonBeanFactoryLocator.getInstance().useBeanFactory(DEFAULT_TEST_FACTORY_KEY).getFactory();
        } catch (BeansException be) {
            this.ctx = new ClassPathXmlApplicationContext(SPRING_BEANS_XML);
        }
    }

    /**
     * C'tor
     * 
     * @param resource String
     */
    public SpringTestCase(final String resource) {
        try {
            this.ctx = SingletonBeanFactoryLocator.getInstance().useBeanFactory(resource).getFactory();
        } catch (BeansException be) {
            this.ctx = new ClassPathXmlApplicationContext(resource);
        }
    }

    /**
     * Prepara os objetos necess�rios: SessionFactory Session dao's
     * 
     * @throws Exception erro
     */
    @Override
    protected void setUp() throws Exception {
        super.setUp();
    }

    /**
     * Libera os recursos utilizados
     * 
     * @throws Exception erro
     */
    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        try {
            if (this.session != null && this.session.isOpen()) {
                if (this.session.getTransaction().isActive()) {
                    this.session.getTransaction().rollback();
                }
                this.session.close();
                this.session = null;
            }
        } finally {

            this.sf = null;

            if (this.ctx != null && this.ctx instanceof DisposableBean) {
                ((DisposableBean) this.ctx).destroy();
                this.ctx = null;
            }
            this.freeFields();
        }
    }

    /**
     * Forces freeing of instance fields
     */
    private void freeFields() {
        final Object target = this;
        for (final Field field : this.getClass().getDeclaredFields()) {
            if (!field.getType().getCanonicalName().startsWith("java.lang")
                    && (field.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) == 0) {
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        field.setAccessible(true);
                        try {
                            field.set(target, null);
                        } catch (IllegalArgumentException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                });
            }
        }
    }

    /**
     * @return the ctx
     */
    public BeanFactory getCtx() {
        return this.ctx;
    }

    /**
     * Get the session, opening if necessary
     * 
     * @return the session
     */
    public Session getSession() {
        if (this.session == null) {
            this.session = this.getSf().openSession();
        }
        if (!this.session.isOpen()) {
            this.session = this.getSf().openSession();
        }
        return this.session;
    }

    /**
     * Devolve a sessao atual com o interceptor associado
     * 
     * @param interceptor Interceptor
     * @return Session
     */
    public Session getSession(final Interceptor interceptor) {
        this.flushSession();
        this.session = this.getSf().openSession(interceptor);
        return this.session;
    }

    /**
     * Fecha a sess�o atual
     */
    private void flushSession() {
        if (this.session == null || !this.session.isOpen()) {
            return;
        }
        if (this.session.getTransaction() != null && this.session.getTransaction().isActive()) {
            this.session.getTransaction().commit();
        }
        this.session.flush();
        this.session.close();
    }

    /**
     * Binds the hibernate session with the current Thread
     */
    public final void bindSession() {
        TransactionSynchronizationManager.bindResource(this.getSf(), new SessionHolder(this.getSession()));
    }

    /**
     * Unbinds the hibernate session associated with the current Thread
     */
    public final void unbindSession() {
        if (this.sf != null && this.session != null) {
            TransactionSynchronizationManager.unbindResource(this.sf);
        }
    }

    /**
     * @return the sf
     */
    public SessionFactory getSf() {
        if (this.sf == null) {
            this.sf = (SessionFactory) this.ctx.getBean("sessionFactory");
        }
        return this.sf;
    }

    /**
     * Destroy sf
     */
    public void destroySf() {
        if (this.sf != null) {
            this.sf.close();
            this.sf = null;
        }
    }

}
